package video.com.recommend;

import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.similarity.ItemSimilarity;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import video.com.mapper.UserBehaviorMapper;
import video.com.utils.ConvertDateUtils;
import video.com.utils.ThreadPool;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

@Component
public class Recommend {

    @Value("${file.csv}")
    private String filePath;

    private File file;

    @Resource
    private UserBehaviorMapper userBehaviorMapper;

    @Autowired
    private RedissonClient redissonClient;

    @PostConstruct
    public void ItemCF(){
        //准备数据 这里是电影评分数据
        try{
            file = new File(filePath + "ratings.csv");
        }catch (Exception e){
            e.printStackTrace();
            file = null;
        }

    }

    /**
     * 基于物品的用户推荐
     * @param userId
     * @param videoId
     * @throws Exception
     */
    public List<Long> itemRecommend(int userId , int videoId) throws Exception {

        List<Long> lists = new ArrayList<>();

        if(file == null){
            return lists;
        }

        if(!file.exists()){
            return lists;
        }
        // 实例化DataModel并将数据传入其内
        DataModel dataModel = null;
        try {
            dataModel = new FileDataModel(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 计算相似度
        ItemSimilarity itemSimilarity = null;
        try {
            itemSimilarity = new PearsonCorrelationSimilarity(dataModel);
        } catch (TasteException e) {
            e.printStackTrace();
        }
        // 构建推荐器，使用基于物品的协同过滤推荐
        GenericItemBasedRecommender recommender = new GenericItemBasedRecommender(dataModel, itemSimilarity);

        List<RecommendedItem> recommendedItemList = null;
        try {
            // 计算用户2当前浏览的商品2，推荐20个相似的商品
            recommendedItemList = recommender.recommendedBecause(userId, videoId, 20);
            lists = recommendedItemList.stream().map(item -> item.getItemID()).collect(Collectors.toList());
        } catch (TasteException e) {
            e.printStackTrace();
            throw e;
        }

        return lists;
    }

    /**
     * 基于用户推荐 video
     * @param userId
     * @return
     * @throws Exception
     */
    public List<Long> userRecommend(int userId , Set<Integer> notIncludes) throws Exception{

        List<Long> lists = new ArrayList<>();

        if(file == null){
            return lists;
        }

        if(!file.exists()){
            return lists;
        }

        // 实例化DataModel并将数据传入其内
        DataModel dataModel = null;
        try {
            dataModel = new FileDataModel(file);
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        }
        // 计算相似度
        UserSimilarity userSimilarity = null;
        try {
            userSimilarity = new PearsonCorrelationSimilarity(dataModel);
        } catch (TasteException e) {
            e.printStackTrace();
        }
        //计算最近邻域，邻居有两种算法，基于固定数量的邻居和基于相似度的邻居，这里使用基于固定数量的邻居
        UserNeighborhood userNeighborhood = new NearestNUserNeighborhood(100, userSimilarity, dataModel);
        // 构建推荐器，使用基于用户的协同过滤推荐
        GenericUserBasedRecommender recommender = new GenericUserBasedRecommender(dataModel,userNeighborhood, userSimilarity);

        List<RecommendedItem> recommendedItemList = null;
        try {
            // 计算用户userId当前浏览的商品2，推荐20个相似的商品
            recommendedItemList = recommender.recommend(userId, notIncludes.size() + 20);
            lists = recommendedItemList.stream().map(item -> item.getItemID()).filter(item -> !notIncludes.contains(item)).collect(Collectors.toList());
        } catch (TasteException e) {
            e.printStackTrace();
            throw e;
        }

        return lists;

    }

    /**
     * 用户应为记录操作更新
     */
    @Scheduled(cron = "0 3 * * * ?")
    public void updateUserCommend(){

        int count = userBehaviorMapper.getUserBehaviorToVideoCount();
        // 用户行为记录表中的数量小于一定值时 不进行下列操作
        if(count < 10000){
            return;
        }
        RLock updateCvsFile = redissonClient.getLock("updateCvsFile");

        BufferedWriter writer = null;

        if(!file.exists()){
            file.mkdirs();
        }

        try{
            if(! updateCvsFile.tryLock()){
                return ;
            }

            if(file == null){

            }

            FileWriter fileWriter = new FileWriter(file);
            writer = new BufferedWriter(fileWriter);

            int l = count / 10000;
            if (count % 10000 != 0) {
                l ++;
            }

            List<Future<List<Map<String, Object>>>> lists = new ArrayList<Future<List<Map<String, Object>>>>(l);

            for (int i = 0; i < l; i++) {
                int finalI = i;
                int finalI1 = i;
                lists.add(ThreadPool.pool.submit(() -> {
                    return userBehaviorMapper.getUserBehaviorToVideo(finalI, (finalI1 + 1) * 1000);
                }));
            }


            for(Future<List<Map<String, Object>>> future : lists){
                try {
                    List<String> lines = future.get().stream().map(item -> {
                        StringBuilder str = new StringBuilder();
                        str.append(item.get("userId").toString());
                        str.append(",");
                        str.append(item.get("videoId").toString());
                        str.append(",");
                        str.append(item.get("recommend").toString());
                        str.append(",");
                        str.append(ConvertDateUtils.unixTimeStamp(item.get("behaviorTime").toString()));
                        return str.toString();
                    }).collect(Collectors.toList());

                    for(String str: lines){
                        writer.write(str);
                    }
                    writer.flush();
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        } finally {
            if (updateCvsFile.isHeldByCurrentThread()) {
                updateCvsFile.unlock();
            }

            if(writer != null){
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }



    }
}
